﻿using System;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.TeamFoundation.Common;
using Microsoft.TeamFoundation.Framework.Server;
using Microsoft.TeamFoundation.Server;
using Microsoft.TeamFoundation.VersionControl.Server;
using CodePlex.TfsPluginSuite.Configuration;
using CodePlex.TfsPluginSuite.Helper;

namespace CodePlex.TfsPluginSuite.Policies
{
    /// <summary>
    /// Ensures that the associated work items to a checkin comply with standards that you set. The following standards can be enforced:
    ///     - Specify work item queries whose results will be the only legal work items for a check-in to be associated with. 
    ///       (e.g. Require an active task that is assigned to the submitter.)
    ///     - Stop check-ins if the associated work items match the results from a work item query. 
    ///       (e.g. Deny direct assignment to work items categorized as requirements.)
    ///     - Limit the work in progress so that each commit of code reflects one task. 
    ///       (e.g. Enforce that only a single task is used for each check-in.)
    ///     - Ensure associated work is set to a current iteration. Iterations classified a past or future 
    ///       (determined by the iteration's start and end dates) will not be allowed.
    /// </summary>
    public class WorkItemAssociationPolicy : ISubscriber
    {
        public string Name
        {
            get { return typeof(WorkItemAssociationPolicy).FullName; }
        }

        public SubscriberPriority Priority
        {
            get { return SubscriberPriority.Normal; }
        }

        public Type[] SubscribedTypes()
        {
            return new Type[1] { typeof(CheckinNotification) };
        }

        public EventNotificationStatus ProcessEvent(TeamFoundationRequestContext requestContext,
            NotificationType notificationType,
            object notificationEventArgs,
            out int statusCode,
            out string statusMessage,
            out ExceptionPropertyCollection properties)
        {
            properties = new ExceptionPropertyCollection();
            statusMessage = String.Empty;
            statusCode = 0;

            var config = TfsPluginSuiteConfigurationManager.Section.WorkItemAssociationPolicy;

            //don't run this policy if it is disabled in the config file
            if (!config.Enabled)
            {
                return EventNotificationStatus.ActionPermitted;
            }

            // If this is a check-in notification and is a decision point
            if (notificationEventArgs is CheckinNotification && notificationType == NotificationType.DecisionPoint)
            {
                var notification = notificationEventArgs as CheckinNotification;

                //this policy doesn't apply to first class branches because there is no way in the client UI to associate a work item with the changeset
                //we should consider using the TFS Merge work item Event Handler on CodePlex for associating work items with branches/merges
                if (notification.CheckinType != CheckinType.Branch)
                {
                    var associatedWorkItems = notification.NotificationInfo.WorkItemInfo;

                    //This policy is only useful if we have associated work items
                    if (associatedWorkItems.Count() == 0)
                    {
                        statusMessage = "You must associate this check-in with a work item.";
                        return EventNotificationStatus.ActionDenied;
                    }

                    using (var tfsHelper = new TfsHelper(requestContext, notification))
                    {
                        //we always want to make sure each associated work item is valid, thats why it is in a separate loop
                        foreach (var workItem in associatedWorkItems.Select(wi => tfsHelper.GetWorkItem(wi.Id)))
                        {
                            if (!workItem.IsValid())
                            {
                                statusMessage = workItem.ToShortString() + " cannot be saved.  Open and resolve issues that prevent saving.";
                                return EventNotificationStatus.ActionDenied;
                            }
                        }

                        //Denied work item policy
                        if (config.DeniedWorkItemsPolicy.Enabled)
                        {
                            foreach (DenyWorkItemsConfigurationElement denyWorkItems in config.DeniedWorkItemsPolicy)
                            {
                                //In order to support link queries and have better performance, let's run the the RunCountQuery
                                int queryCount = tfsHelper.GetWorkItemQueryResultCount(denyWorkItems.WorkItemQuery);

                                if (queryCount > 0)
                                {
                                    statusMessage = denyWorkItems.Message;
                                    return EventNotificationStatus.ActionDenied;
                                }
                            }
                        }

                        //Required work item policy
                        if (config.RequiredWorkItemsPolicy.Enabled)
                        {
                            foreach (RequiredWorkItemsConfigurationElement requiredWorkItems in config.RequiredWorkItemsPolicy)
                            {
                                int queryCount = tfsHelper.GetWorkItemQueryResultCount(requiredWorkItems.WorkItemQuery);

                                if (queryCount == 0 || queryCount > requiredWorkItems.WorkInProgressLimit)
                                {
                                    statusMessage = requiredWorkItems.Message;
                                    return EventNotificationStatus.ActionDenied;
                                }

                                if (requiredWorkItems.CheckIterationDates)
                                {
                                    foreach (var workItemInfo in associatedWorkItems)
                                    {
                                        var workItem = tfsHelper.GetWorkItem(workItemInfo.Id);
                                        if (!workItem.IterationPath.Contains('\\'))
                                        {
                                            statusMessage = workItem.ToShortString() + " cannot be assigned to the root iteration";
                                            return EventNotificationStatus.ActionDenied;
                                        }
                                        NodeInfo iterationNodeInfo = tfsHelper.GetIterationInfo(workItem);
                                        var iterationStartDate = iterationNodeInfo.StartDate;
                                        var iterationFinishDate = iterationNodeInfo.FinishDate;

                                        if (iterationStartDate == null || iterationFinishDate == null)
                                        {
                                            statusMessage = string.Format("{0} is on iteration \"{1}\" which is missing either a start or finish date",
                                                workItem.ToShortString(),
                                                workItem.IterationPath);
                                            return EventNotificationStatus.ActionDenied;
                                        }
                                        else if (DateTime.Now.Date < iterationStartDate.Value.Date)
                                        {
                                            statusMessage = string.Format("{0} is on iteration \"{1}\" which does not start until {2}",
                                                workItem.ToShortString(),
                                                workItem.IterationPath,
                                                iterationStartDate.Value.ToShortDateString());
                                            return EventNotificationStatus.ActionDenied;
                                        }
                                        else if (DateTime.Now.Date > iterationFinishDate.Value.Date)
                                        {
                                            statusMessage = string.Format("{0} is on iteration \"{1}\" which was finished as of {2}",
                                                workItem.ToShortString(),
                                                workItem.IterationPath, iterationFinishDate.Value.ToShortDateString());
                                            return EventNotificationStatus.ActionDenied;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            // Passed all the checks of this policy so permit the checkin action
            return EventNotificationStatus.ActionPermitted;
        }
    }
}

